/*******************************************************************************
 * Copyright (c) 2001, 2002 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.core;

import java.util.ArrayList;
import java.util.List;

import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.api.IPerforceServerManager;
import net.sourceforge.perforce.core.internal.BackgroundUpdateRunnable;
import net.sourceforge.perforce.core.internal.ConnectionMonitorRunnable;
import net.sourceforge.perforce.core.internal.IWorkspaceHelper;
import net.sourceforge.perforce.core.internal.Policy;
import net.sourceforge.perforce.core.internal.api.PerforceServerManager;
import net.sourceforge.perforce.core.internal.events.AddDeleteMoveListener;
import net.sourceforge.perforce.core.internal.events.MetaFileChangeListener;
import net.sourceforge.perforce.core.resources.IChangelist;
import net.sourceforge.perforce.core.syncinfo.IResourceStateDelta;

import org.eclipse.core.internal.runtime.InternalPlatform;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IPluginDescriptor;
import org.eclipse.core.runtime.IPluginRegistry;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.PluginVersionIdentifier;
import org.eclipse.core.runtime.Status;

/**
  * @version $Revision: 255 $
  * @author  <a href="mailto:bpruessmann@users.sourceforge.net>Boris Pruessmann</a>
  */
public class PerforceCorePlugin extends Plugin 
{
	// ---------------------------------------------------------------------------- Public Constants

	/** The identifier of the plugin */	
	public static final String ID = "net.sourceforge.perforce.core";                   //$NON-NLS-1$

	// --------------------------------------------------------------------------- Private Constants

	/** Identifier for the plugin's nature. */
	private static final String NATURE_ID =	ID + ".perforcenature";                    //$NON-NLS-1$

	// -------------------------------------------------------------------- Private Static Variables

	/** Stores the plugin's singleton instance. */
	private static PerforceCorePlugin plugin;	
	/** Stores the workspace helper instance. */
	private static IWorkspaceHelper workspaceHelper;
		
	// --------------------------------------------------------------------------- Private Variables

	/** Stores the PerforceServerManager. */
	private PerforceServerManager serverManager;
	
	/** Contains a list of IResourceStateChangeListeners. */
	private List resourceStateChangeListenerList = new ArrayList();
	
	/** Stores a list of IChangelistListeners. */
	private List changelistListenerList = new ArrayList();
	
	/** Stores a list of IClientUserListeners. */
	private List clientUserListenerList = new ArrayList();
	
	/** Stores the connection monitor runnable. */
	private ConnectionMonitorRunnable connectionMonitor;
	
	/** Stores the background update runnable. */
	private BackgroundUpdateRunnable backgroundUpdateRunnable;
	
	/** Stores the AddDeleteMoveListener. */
	private AddDeleteMoveListener addDeleteMoveListener = new AddDeleteMoveListener();
	
	/** Stores the MetaFileChangeListener. */
	private MetaFileChangeListener metaFileChangeListener = new MetaFileChangeListener();
	
	/** true if tasks should be generated, fals otherwise. */
	private boolean showTasksOnAdd = false;
	 
	/** Indicates whether delete operations should be performed the 'perforce way'. */ 
	private boolean deleteAffectsChangelist = true;
	
	/** Stores the interval for background cache updates. */
	private int backgroundUpdateInterval;
	
	/** Indicates whether the Background Update Thread should monitor changlists. */
	private boolean shouldMonitorChangelists;
	
	/** Indicates whether the Background Update Thread should monitor opened files. */ 
	private boolean shouldMonitorOpenedFiles;
	
	/** Submit validators */
	private ISubmitValidator[] submitValidators;
	
	/** Open validators */
	private IOpenValidator[] openValidators;
	
	// ----------------------------------------------------------------------- Public Static Methods
	
	/** Returns the plugin's singleton instance.
	  * 
	  * @return the plugin singleton.
	  */
	public static PerforceCorePlugin getPlugin()
	{
		return plugin;
	}

	/** Returns the plugin's nature id. 
	  * 
	  * @return the plugin's nature id.
	  */	
	public static String getTypeId()
	{
		return NATURE_ID;
	}

	/** Returns the workspace instance. 
	  * 
	  * @return the workspace instance.
	  */
	public static IWorkspace getWorkspace() 
	{
		return ResourcesPlugin.getWorkspace();
	}
	
	/** Returns the plugin's synchronizer singleton instance.
	  * 
	  * @return the synchronizer singleton.
	  */	
	public static PerforceSynchronizer getSynchronizer()
	{
		return PerforceSynchronizer.getInstance();
	}

	/** Call this method to log the specified status object to the eclipse log.
	  * 
	  * @param status IStatus to be logged.
	  */
	public static void log(IStatus status)
	{
		getPlugin().getLog().log(status);
	}

	/** Call this method to log the specified error message with the associated exception.
	  *  
	  * @param message detailed error message.
	  * @param throwable exception that occured.
	  */
	public static void logError(String message, Throwable throwable) 
	{
		getPlugin().getLog().log(
			new Status(IStatus.ERROR, PerforceCorePlugin.ID, IStatus.ERROR, message, throwable));
	}
	
	/** Returns the the ServerManager singleton to the user.
	  * 
	  * @return the ServerManager singleton.
	  */
	public static IPerforceServerManager getServerManager()
	{
		return getPlugin().serverManager;
	}
	
	/** Called to retrieve the workspace helper.
	  * 
	  * @return the workspace helper.
	  */
	public static IWorkspaceHelper getWorkspaceHelper()
	{
		if (workspaceHelper == null)
		{
			final PluginVersionIdentifier version = new PluginVersionIdentifier(2, 1, 0);
			final PluginVersionIdentifier resourcesVersion = 
				ResourcesPlugin.getPlugin().getDescriptor().getVersionIdentifier();			
			if (resourcesVersion.isCompatibleWith(version))                        // Eclipse >= 2.1
			{
				workspaceHelper = new IWorkspaceHelper() 
				{
					public IFile[] findFilesForLocation(IPath path) 
					{
						final IWorkspaceRoot root = getWorkspace().getRoot();
						return root.findFilesForLocation(path);
					}
				};
			}
			else                                                                   // Eclipse >= 2.0
			{
				workspaceHelper = new IWorkspaceHelper() 
				{
					public IFile[] findFilesForLocation(IPath path) 
					{
						final IWorkspaceRoot root = getWorkspace().getRoot();
						return new IFile[]{ root.getFileForLocation(path) };
					}
				};
			}
		}
		
		return workspaceHelper;
	}
	
	// ------------------------------------------------------------------------- Public Constructors

	/** Constructor for PerforceCorePlugin.
	  * 
	  * @param descriptor the Plugin's descriptor.
	  */
	public PerforceCorePlugin(IPluginDescriptor descriptor) 
	{
		super(descriptor);
		plugin = this;
	}
	
	// ------------------------------------------------------------------------------ Public Methods
	
	/** Returns whether deletes should be performed the perforce way.
	  * 
	  * @return true or false.
	  */
	public boolean getDeleteAffectsChangelist()
	{
		return deleteAffectsChangelist;
	}
	
	/** Changes the way delete operations are performed.
	  *
	  * @param newDeleteAffectsChangelist true means that we do deletes the perforce way. 
	  */ 
	public void setDeleteAffectsChangelist(boolean newDeleteAffectsChangelist)
	{
		deleteAffectsChangelist = newDeleteAffectsChangelist;
	}
	
	/** Returns whether tasks should be generated or not.
	  *
	  * @return true if tasks should be generated. 
	  */
	public boolean getShowTasksOnAdd()
	{
		return showTasksOnAdd;
	}
	
	/** Sets whether tasks should be generated or not.
	  *
	  * @param newShowTasksOnAdd true or false. 
	  */
	public void setShowTasksOnAdd(boolean newShowTasksOnAdd)
	{
		showTasksOnAdd = newShowTasksOnAdd;
	}
	
	/** Gets the interval for background updates.
	  *
	  * @return background update interval.  
	  */
	public int getBackgroundUpdateInterval()
	{
		return backgroundUpdateInterval;
	}
	
	/** Sets the background update interval.
	  *
	  * @param interval the new background update interval.  
	  */
	public void setBackgroundUpdateInterval(int interval)
	{
		backgroundUpdateInterval = interval;
	}
	
	/** Checks whether the Background Update Thread should watch changelists.
	  *
	  * @return true or false. 
	  */
	public boolean shouldMonitorChangelists()
	{
		return shouldMonitorChangelists;
	}
	
	/** Changes the Background Update Monitors behavior concerning the monitoring of changelists.
	  *
	  * @param newValue the new value. 
	  */
	public void setShouldMonitorChangelists(boolean newValue)
	{
		shouldMonitorChangelists = newValue;
	}

	/** Checks whether the Background Update Thread should watch opened files.
	  *
	  * @return true or false. 
	  */	
	public boolean shouldMonitorOpenedFiles()
	{
		return shouldMonitorOpenedFiles;
	}

	/** Changes the Background Update Monitors behavior concerning the monitoring of opened files.
	  *
	  * @param newValue the new value. 
	  */	
	public void setShouldMonitorOpenedFiles(boolean newValue)
	{
		shouldMonitorOpenedFiles = newValue;
	}

	/** If this returns true, the system will try to refresh sync info from the server
	  * when the cache does not contain anything.
	  * 
	  * @return true or false.
	  * 
	  * TODO: Preferences for this feature
	  */
	public boolean getShouldRefreshWhenNotFound()
	{
		return false;
	}

	/** @see Plugin#startup() */
	public void startup() 
		throws CoreException 
	{
		super.startup();
		Policy.localize("net.sourceforge.perforce.core.messages");                     //$NON-NLS-1$
	
		try
		{	
			serverManager = new PerforceServerManager();
			serverManager.startup();
			
			connectionMonitor = new ConnectionMonitorRunnable();
			connectionMonitor.startup();
			
			backgroundUpdateRunnable = new BackgroundUpdateRunnable();
			backgroundUpdateRunnable.startup();
			
			PerforceSynchronizer.getInstance().startup();
		}
		catch (PerforceException e)
		{
			throw new CoreException(e.getStatus());
		}
				
		PerforceCorePlugin.getWorkspace().addResourceChangeListener(
			addDeleteMoveListener, 
			IResourceChangeEvent.POST_AUTO_BUILD | IResourceChangeEvent.PRE_DELETE);
		addResourceStateChangeListener(addDeleteMoveListener);
		
		PerforceCorePlugin.getWorkspace().addResourceChangeListener(
			metaFileChangeListener, IResourceChangeEvent.PRE_AUTO_BUILD);
	}
	
	/** @see Plugin#shutdown() */
	public void shutdown() 
		throws CoreException 
	{
		PerforceCorePlugin.getWorkspace().removeResourceChangeListener(metaFileChangeListener);
		PerforceCorePlugin.getWorkspace().removeResourceChangeListener(addDeleteMoveListener);

		try
		{
			backgroundUpdateRunnable.shutdown();
			connectionMonitor.shutdown();
			
			PerforceSynchronizer.getInstance().shutdown();
			
			serverManager.shutdown();
		}
		catch (PerforceException e)
		{
			throw new CoreException(e.getStatus());
		}		
		
		super.shutdown();
	}


	/** Adds the given IResourceStateChangeListener to the notification list.
	  * 
	  *  @param listener the IResourceStateChangeListener to be added.
	  */
	public void addResourceStateChangeListener(IResourceStateChangeListener listener)
	{
		resourceStateChangeListenerList.add(listener);
	}
	
	/** Removes the specified IResourceStateChangeListener from the notification list.
	  *
	  * @param listener the listener that should be removed.
	  */
	public void removeResourceStateChangeListener(IResourceStateChangeListener listener)
	{
		resourceStateChangeListenerList.remove(listener);	
	}	
	
	/** Call this method to notify registers IResourceStateChangeListeners that
	  * the given resources have changed their state.
	  * 
	  * @param deltas the ResourceStateDeltas.
	  * 
	  * @return the status of the operation.
	  */
	public IStatus broadcastResourceStateChanges(final IResourceStateDelta[] deltas)
	{
		final MultiStatus result = 
			new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null);              //$NON-NLS-1$
		for (int i = 0, length = resourceStateChangeListenerList.size(); i < length; i++)
		{
			final IResourceStateChangeListener listener = 
				(IResourceStateChangeListener) resourceStateChangeListenerList.get(i);

			Runnable runnable = new Runnable()
			{
				public void handleException(Throwable exception)
				{
					logError("Error in broadcastResourceStateChanges()", exception);
				}

				public void run() 
				{
					try
					{
						listener.resourceStateChanged(deltas);
					}
					catch (Throwable e)
					{
						handleException(e);
					}
				}
			};

			new Thread(runnable).start();
		}
		
		return result;
	}

	/** Call this method to notfiy registered IResourceStateChangeListeners about changes
	  * to the given changelists.
	  * 
	  * @param changelists the changelists. 
	  */
	public void broadcastChangelistStateChanges(final IChangelist[] changelists)
	{
		for (int i = 0, length = resourceStateChangeListenerList.size(); i < length; i++)
		{
			final IResourceStateChangeListener listener = 
				(IResourceStateChangeListener) resourceStateChangeListenerList.get(i);

			ISafeRunnable runnable = new ISafeRunnable()
			{
				/** @see ISafeRunnable#handleException(Throwable) */
				public void handleException(Throwable exception)
				{
					logError("Error in broadcastChangelistStateChanges()", exception);
				}

				/** @see ISafeRunnable#run() */
				public void run() 
					throws Exception
				{
					listener.changelistStateChanged(changelists);
				}
			};

			Platform.run(runnable);
		}
	}

	/** Call this method to notify registered listeners about changes to the Changelists of the
	  * given servers.
	  * 
	  * @param servers the servers. 
	  */
	public void broadcastChangelistStateChanges(final IPerforceServer[] servers)
	{
		for (int i = 0, length = resourceStateChangeListenerList.size(); i < length; i++)
		{
			final IResourceStateChangeListener listener = 
				(IResourceStateChangeListener) resourceStateChangeListenerList.get(i);

			ISafeRunnable runnable = new ISafeRunnable()
			{
				/** @see ISafeRunnable#handleException(Throwable) */
				public void handleException(Throwable exception)
				{
					logError("Error in broadcastChangelistStateChanges()", exception);
				}

				/** @see ISafeRunnable#run() */
				public void run() 
					throws Exception
				{
					listener.changelistStateChanged(servers);
				}
			};

			Platform.run(runnable);
		}
	}

	/** Adds the given listener to the Changelist notification list.
	  *
	  * @param listener that should be added.
	  */
	public void addChangelistListener(IChangelistListener listener)
	{
		changelistListenerList.add(listener);
	}

	/** Removes the pecified listener from the Changelist notification list.
	  * 
	  * @param listener to remove.
	  */
	public void removeChangelistListener(IChangelistListener listener)
	{
		changelistListenerList.remove(listener);
	}
	
	/** Adds the given IClientUserListener to the listener list.
	  * 
	  * @param listener the listener to add.
	  */
	public void addClientUserListener(IClientUserListener listener)
	{
		clientUserListenerList.add(listener);
	}
	
	/** Removes the given IClientUserListener from the listener list.
	  * 
	  * @param listener the listener to add.
	  */
	public void removeClientUserListener(IClientUserListener listener)
	{
		clientUserListenerList.remove(listener);
	}
	
	/** Returns an array of client user listeners.
	  * 
	  * @return array of listeners.
	  */
	public IClientUserListener[] getClientUserListeners()
	{
		final IClientUserListener[] result = new IClientUserListener[clientUserListenerList.size()];
		return (IClientUserListener[]) clientUserListenerList.toArray(result);
	}

	/** Notifies that registerd listeners taht one or more changelist have been changed. */
	public void broadcastChangelistChanges()
	{
		for (int i = 0, length = changelistListenerList.size(); i < length; i++)
		{
			final IChangelistListener listener = 
				(IChangelistListener) changelistListenerList.get(i);
				
			ISafeRunnable runnable = new ISafeRunnable() 
			{
				/** @see ISafeRunnable#handleException(Throwable) */
				public void handleException(Throwable exception) 
				{
					logError("Error in broadcastChangelistChanges()", exception);
				}

				/** @see ISafeRunnable#run() */
				public void run() throws Exception 
				{
					listener.notifyChange();
				}
			};
			Platform.run(runnable);
		}
	}
	
	/** Called to retrieve an array of submit validators.
	  *
	  * @return array of submit validators. 
	  */
	public synchronized ISubmitValidator[] getSubmitValidators()
	{
		if (submitValidators == null)
		{
			submitValidators = computeSubmitValidators();
		}
		
		return submitValidators;
	}
	
	/** Called to retrieve an array of open validators.
	  * 
	  * @return array of open validators.
	  */
	public synchronized IOpenValidator[] getOpenValidators()
	{
		if (openValidators == null)
		{
			openValidators = computeOpenValidators();
		}
		
		return openValidators;
	}
	
	// ----------------------------------------------------------------------------- Private Methods
	
	/** Called to compute the submit validators.
	  * 
	  * @return array of submit validators. 
	  */
	private ISubmitValidator[] computeSubmitValidators()
	{
		IPluginRegistry registry = Platform.getPluginRegistry();
		IExtensionPoint extensionPoint = 
			registry.getExtensionPoint(PerforceCorePlugin.ID, "submitValidators");
		IConfigurationElement[] configurationElements = extensionPoint.getConfigurationElements();
		
		ArrayList result = new ArrayList(configurationElements.length);
		for (int i = 0; i < configurationElements.length; i++)
		{
			IConfigurationElement element = configurationElements[i];
			try 
			{
				ISubmitValidator validator =
					(ISubmitValidator) element.createExecutableExtension("class");
				result.add(validator);
			} 
			catch (CoreException e) 
			{
				PerforceCorePlugin.log(e.getStatus());
			}
		}
		
		return (ISubmitValidator[]) result.toArray(new ISubmitValidator[result.size()]);
	}
	
	/** Called to compute the open validators.
	  * 
	  * @return array of open validators. 
	  */
	private IOpenValidator[] computeOpenValidators()
	{
		IPluginRegistry registry = Platform.getPluginRegistry();
		IExtensionPoint extensionPoint = 
			registry.getExtensionPoint(PerforceCorePlugin.ID, "openValidators");
		IConfigurationElement[] configurationElements = extensionPoint.getConfigurationElements();
	
		ArrayList result = new ArrayList(configurationElements.length);
		for (int i = 0; i < configurationElements.length; i++)
		{
			IConfigurationElement element = configurationElements[i];
			try 
			{
				IOpenValidator validator =
					(IOpenValidator) element.createExecutableExtension("class");
				result.add(validator);
			} 
			catch (CoreException e) 
			{
				PerforceCorePlugin.log(e.getStatus());
			}
		}
	
		return (IOpenValidator[]) result.toArray(new IOpenValidator[result.size()]);
	}
}
